/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package NodeManager;

import Global.Data;
import Rdf.RdfCommon;
import Rdf.RdfReader;
import Rdf.RdfWriter;
import com.hp.hpl.jena.rdf.model.Bag;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.NodeIterator;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.ResIterator;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.SimpleSelector;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import fileman.MainGUI;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Sean
 */
public class RdfModelManager {

    private Node _node1;
    private Node _node2;
    private Node _node3;
    private Model _rdfModel;
    private File _rdfModelFile;

    public RdfModelManager(Node node1, Node node2, Node node3) throws Exception {
        _rdfModel = ModelFactory.createDefaultModel();
        InitNodes(node1, node2, node3);
        _rdfModelFile = new File(Data.NodesDirectory + File.separatorChar + Data.DefaultRdfFileName);
        SaveRdfModel(_rdfModelFile);

    }

    public RdfModelManager(File rdfModelFile) {

        try {
            _rdfModel = ModelFactory.createDefaultModel();
            _rdfModel.read(new FileInputStream(rdfModelFile), null);

            try {
                _node1 = RdfReader.GetNode(_rdfModel, Global.Data.Node1Name);
            } catch (FileNotFoundException e) {
            }
            try {
                _node2 = RdfReader.GetNode(_rdfModel, Global.Data.Node2Name);
            } catch (FileNotFoundException e) {
            }
            try {
                _node3 = RdfReader.GetNode(_rdfModel, Global.Data.Node3Name);
            } catch (FileNotFoundException e) {
            }
            _rdfModelFile = rdfModelFile;
            SaveRdfModel(_rdfModelFile);

        } catch (Exception e) {
            Logger.getLogger(RdfModelManager.class.getName()).log(Level.SEVERE, null, e);
        }

    }

    public Node GetNode1() {

        try {
            return _node1;
        } catch (Exception e) {
            return null;
        }

    }

    public Node GetNode2() {

        try {
            return _node2;
        } catch (Exception e) {
            return null;
        }
    }

    public Node GetNode3() {

        try {
            return _node3;
        } catch (Exception e) {
            return null;
        }
    }

    private void InitNodes(Node node1, Node node2, Node node3) {

        if (node1.NodeName.compareToIgnoreCase(node2.NodeName) == 0 ||
                node1.NodeName.compareToIgnoreCase(node3.NodeName) == 0 ||
                node2.NodeName.compareToIgnoreCase(node3.NodeName) == 0) {
            throw new IllegalArgumentException("All nodes must have unique names.");
        }

        _node1 = node1;
        _node2 = node2;
        _node3 = node3;

        RdfWriter.AddNode(_rdfModel, node1);
        RdfWriter.AddNode(_rdfModel, node2);
        RdfWriter.AddNode(_rdfModel, node3);

    }

    protected Model GetRdfModel() {
        return _rdfModel;
    }

    protected File SaveRdfModel(File rdfModelFile) {

        try {
            if (!rdfModelFile.exists()) {
                rdfModelFile.createNewFile();
            }

            _rdfModel.write(new FileOutputStream(rdfModelFile, false), "RDF/XML-ABBREV");

        } catch (Exception e) {
            Logger.getLogger(RdfModelManager.class.getName()).log(Level.SEVERE, null, e);
        }

        return rdfModelFile;

    }

    protected boolean ManagedFileExists(File file) {

        ManagedFile managedFile = GetManagedFile(file.getName());
        if (managedFile == null) {
            return false;
        } else {
            return true;
        }

    }

    protected void AddFileToOneNode(File file, Node node, boolean addToPhysicalNode) throws IOException {
        // Add a file to one specified node
        AddFileToNode(file, node, addToPhysicalNode);
    }

    protected void AddFileToOneOfTwoNodes(File file, Node firstNode, Node secondNode, boolean addToPhysicalNode) throws IOException {
        // Pick the best node out of two selected nodes to add a file to.
        if (firstNode.GetFileCount() <= secondNode.GetFileCount()) {
            AddFileToNode(file, firstNode, addToPhysicalNode);
        } else {
            AddFileToNode(file, secondNode, addToPhysicalNode);
        }
    }

    public void AddFile(File file, boolean addToPhysicalNode) throws IOException {

        Node firstNode = null;
        Node secondNode = null;

        if (!_node1.NodeAvailable() && !_node2.NodeAvailable() && !_node3.NodeAvailable()) {
            // No Nodes are available!
            return;
        }

        // If at least one of the nodes is currently unavailable,
        // then add it to the other two nodes.
        if (!_node1.NodeAvailable() || !_node2.NodeAvailable() || !_node3.NodeAvailable()) {
            if (_node1.NodeAvailable()) {
                firstNode = _node1; // Node 1
                if (_node2.NodeAvailable()) {
                    secondNode = _node2; // Nodes 1 & 2
                } else {
                    if (_node3.NodeAvailable()) {
                        secondNode = _node3; // Nodes 1 & 3
                    }
                }
            } else if (_node2.NodeAvailable()) {
                firstNode = _node2; // Node 2
                if (_node3.NodeAvailable()) {
                    secondNode = _node3; // Nodes 2 & 3
                }
            } else {
                firstNode = _node3; // Node 3
            }

            if (firstNode != null) {
                AddFileToNode(file, firstNode, addToPhysicalNode);
            }
            if (secondNode != null) {
                AddFileToNode(file, secondNode, addToPhysicalNode);
            }
            return;
        }


        // All Node are available, so find the best 2 nodes to add the file into.
        if (_node1.GetFileCount() <= _node2.GetFileCount() && _node1.GetFileCount() <= _node3.GetFileCount()) {
            // Node 1 has the lowest amount of files, therefore use Node1 as the First Node
            firstNode = _node1;
            if (_node2.GetFileCount() <= _node3.GetFileCount()) {
                //Node2 has the second lowest amount of files, therefore use Node2 as the second node
                secondNode = _node2;
            } else {
                secondNode = _node3;
            }

        } else if (_node2.GetFileCount() <= _node1.GetFileCount() && _node2.GetFileCount() <= _node3.GetFileCount()) {
            firstNode = _node2;
            if (_node1.GetFileCount() <= _node3.GetFileCount()) {
                secondNode = _node1;
            } else {
                secondNode = _node3;
            }

        } else if (_node3.GetFileCount() <= _node1.GetFileCount() && _node3.GetFileCount() <= _node2.GetFileCount()) {
            firstNode = _node3;
            if (_node1.GetFileCount() <= _node2.GetFileCount()) {
                secondNode = _node1;
            } else {
                secondNode = _node2;
            }
        }

        AddFileToNode(file, firstNode, addToPhysicalNode);
        AddFileToNode(file, secondNode, addToPhysicalNode);


    }

    private void AddFileToNode(File file, Node node, boolean addToPhysicalNode) throws IOException {
        if (addToPhysicalNode) {
            node.CopyFileToLocation(file, true);
        }
        RdfWriter.AddFile(_rdfModel, file, node);
        SaveRdfModel(_rdfModelFile);

    }

    protected Node GetNodeByName(String nodeName) {

        if (nodeName.compareToIgnoreCase(_node1.NodeName) == 0) {
            return _node1;
        } else if (nodeName.compareToIgnoreCase(_node2.NodeName) == 0) {
            return _node2;
        } else if (nodeName.compareToIgnoreCase(_node3.NodeName) == 0) {
            return _node3;
        } else {
            return null;
        }
    }

    public ManagedFile GetManagedFile(String fileName) {

        long LastModifiedTimeMS = 0;
        String myFileURI = Global.Data.BaseURI + fileName;

        // Get the resource from the model that matches the current filenmae
        Resource file = _rdfModel.getResource(myFileURI);

        Statement ls = file.getProperty(RdfCommon.CreateProperty("LastModified"));
        if (ls == null) {
            return null;
        }

        // Get the Nodes property
        Statement nodes = file.getProperty(RdfCommon.CreateProperty("Nodes"));

        if (nodes == null) {
            return null;
        }

        // Get the bag that is associated with the Nodes property
        Bag bag = nodes.getBag();

        NodeIterator iter = bag.iterator();
        ManagedFile nodeFile = new ManagedFile();

        // Get the nodes that are associated with this NodeFile.
        nodeFile.FirstNode = GetNodeByName(iter.next().toString());
        if (iter.hasNext()) {
            nodeFile.SecondNode = GetNodeByName(iter.next().toString());
        }
        nodeFile.FileName = fileName;
        nodeFile.FileManager = this;

        return nodeFile;

    }

    public Vector<ManagedFile> GetAllManagedFiles(String nodeName) {

        Vector<ManagedFile> allFiles = new Vector<ManagedFile>();

        // TODO this function could probably be optimised a bit more.

        // Get all resources in the model with a property of "Filename"
        ResIterator iter = _rdfModel.listSubjectsWithProperty(RdfCommon.CreateProperty("Filename"));

        while (iter.hasNext()) {
            // Iterate through each resource
            Resource r = iter.nextResource();
            // Get the actual filename property
            Statement fileName = r.getProperty(RdfCommon.CreateProperty("Filename"));

            // Get all Node names associated with this file
            String myFileURI = Global.Data.BaseURI + fileName.getObject().toString();
            Bag nodesBag = _rdfModel.getBag(myFileURI + "/Nodes");

            // Loop through each Node, only add the file if it is a member of the Node
            NodeIterator nodesIter = nodesBag.iterator();
            if (nodesIter.hasNext()) {
                while (nodesIter.hasNext()) {
                    if (nodeName.compareToIgnoreCase(nodesIter.nextNode().toString()) == 0) {
                        // Add the ManagedFile to the vector
                        allFiles.add(GetManagedFile(fileName.getObject().toString()));
                    }
                }
            }
        }

        return allFiles;
    }

    public Vector<ManagedFile> GetAllManagedFiles() {

        Vector<ManagedFile> allFiles = new Vector<ManagedFile>();

        // TODO this function could probably be optimised a bit more.

        // Get all resources in the model with a property of "Filename"
        ResIterator iter = _rdfModel.listSubjectsWithProperty(RdfCommon.CreateProperty("Filename"));

        while (iter.hasNext()) {
            // Iterate through each resource
            Resource r = iter.nextResource();
            // Get the actual filename property
            Statement fileName = r.getProperty(RdfCommon.CreateProperty("Filename"));
            // Add the ManagedFile to the vector
            allFiles.add(GetManagedFile(fileName.getObject().toString()));
        }

        return allFiles;
    }

    public void DeleteFile(String fileName) {

        // Get all Node names associated with this file
        String myFileURI = Global.Data.BaseURI + fileName;
        Bag nodesBag = _rdfModel.getBag(myFileURI + "/Nodes");

        // Loop through each Node
        NodeIterator iter = nodesBag.iterator();
        if (iter.hasNext()) {
            while (iter.hasNext()) {
                // Get the current Node
                String nodeName = iter.nextNode().toString();
                Node node = GetNodeByName(nodeName);
                // Now delete the file from the Node Location
                File file = new File(node.GetLocation() + File.separatorChar + fileName);
                if (file.exists()) {
                    boolean deleted = file.delete();
                    if (!deleted) {
                        System.out.println(file.getName() + " not deleted");
                    } else {
                        System.out.println(file.getName() + " deleted");
                    }
                }
            }
        }

        // Remove the Nodes bag from the model
        nodesBag.removeProperties();
        // Remove the file resource from the Model
        Resource fileResource = _rdfModel.getResource(myFileURI).removeProperties();
        SaveRdfModel(_rdfModelFile);

    }

    protected void DeleteFile(String fileName, String nodeName) {

        // Get all Node names associated with this file
        String myFileURI = Global.Data.BaseURI + fileName;

        Bag nodesBag = _rdfModel.getBag(myFileURI + "/Nodes");

        Node node = GetNodeByName(nodeName);
        // Now delete the file from the Node Location
        File file = new File(node.GetLocation() + File.separatorChar + fileName);
        if (file.exists()) {
            file.delete();
        }

        NodeIterator iter = nodesBag.iterator();
        while (iter.hasNext()) {
            RDFNode node1 = iter.next();

            if (node1.toString().equalsIgnoreCase(nodeName)) {
                iter.remove();
            }
        }

        // Remove the file resource from the Model
        _rdfModel.getResource(myFileURI).removeProperties();
        SaveRdfModel(_rdfModelFile);

    }

    public Vector<ManagedFile> FindFiles(final String fileName) {

        Vector<ManagedFile> managedFiles = new Vector<ManagedFile>();

        StmtIterator iter = _rdfModel.listStatements(
                new SimpleSelector(null, RdfCommon.CreateProperty("Filename"), (RDFNode) null) {

                    @Override
                    public boolean selects(Statement s) {
                        return s.getString().contains(fileName);
                    }
                });

        while (iter.hasNext()) {
            //System.out.println(iter.nextStatement().getObject().toString());
            managedFiles.add(GetManagedFile(iter.nextStatement().getObject().toString()));
        }

        return managedFiles;
    }

    protected boolean checkForDamagedFiles() {

        Boolean repair = false;
        Vector files = GetAllManagedFiles();
        Iterator fileiter = files.iterator();

        while (fileiter.hasNext()) {
            ManagedFile mf = (ManagedFile) fileiter.next();

            if (mf.isDamaged()) {
                mf.repairDamage();
                repair = true;
            }

        }
        if (repair) {
            SaveRdfModel(_rdfModelFile);
        }
        return repair;

    }

    protected boolean checkForModifiedFiles() {
        Vector files = GetAllManagedFiles();
        Iterator fileIter = files.iterator();
        Boolean modified = false;

        while (fileIter.hasNext()) {
            ManagedFile mf = (ManagedFile) fileIter.next();
            mf.updateFileToCurrent();
            modified = true;
        }
        if (modified) {
            SaveRdfModel(_rdfModelFile);
        }
        return modified;

    }

    protected boolean checkForAddedFiles() {

        Boolean added = false;

        File nodeLocation = new File(GetNode1().GetLocation());
        for (File file : nodeLocation.listFiles()) {
            if (!ManagedFileExists(file)) {
                // A file exists in the Node 1 directory, so we had it to the RdfManager.Node1.
                try {

                    AddFileToOneNode(file, GetNode1(), false);

                    // Now add the file to one of the other Nodes:
                    File f2 = new File(GetNode2().GetLocation() + File.separatorChar + file.getName());
                    File f3 = new File(GetNode3().GetLocation() + File.separatorChar + file.getName());
                    if (f2.exists()) {
                        AddFileToNode(file, _node2, false);
                    } else if (f3.exists()) {
                        AddFileToNode(file, _node3, false);
                    } else {
                        AddFileToOneOfTwoNodes(file, GetNode2(), GetNode3(), true);
                    }

                    added = true;
                } catch (IOException e) {
                    Logger.getLogger(MainGUI.class.getName()).log(Level.SEVERE, null, e);
                }
            }
        }

        //Node 2
        nodeLocation = new File(GetNode2().GetLocation());
        for (File file : nodeLocation.listFiles()) {
            if (!ManagedFileExists(file)) {
                try {

                    AddFileToOneNode(file, GetNode2(), false);

                    // Now add the file to one of the other Nodes:
                    File f1 = new File(GetNode1().GetLocation() + File.separatorChar + file.getName());
                    File f3 = new File(GetNode3().GetLocation() + File.separatorChar + file.getName());
                    if (f1.exists()) {
                        AddFileToNode(file, _node1, false);
                    } else if (f3.exists()) {
                        AddFileToNode(file, _node3, false);
                    } else {
                        AddFileToOneOfTwoNodes(file, GetNode1(), GetNode3(), true);
                    }

                    added = true;
                } catch (IOException e) {
                    Logger.getLogger(MainGUI.class.getName()).log(Level.SEVERE, null, e);
                }
            }
        }

        //Node 3
        nodeLocation = new File(GetNode3().GetLocation());
        for (File file : nodeLocation.listFiles()) {
            if (!ManagedFileExists(file)) {
                try {

                    AddFileToOneNode(file, GetNode3(), false);
                    // Now add the file to one of the other Nodes:
                    File f1 = new File(GetNode1().GetLocation() + File.separatorChar + file.getName());
                    File f2 = new File(GetNode2().GetLocation() + File.separatorChar + file.getName());
                    if (f1.exists()) {
                        AddFileToNode(file, _node1, false);
                    } else if (f2.exists()) {
                        AddFileToNode(file, _node2, false);
                    } else {
                        AddFileToOneOfTwoNodes(file, GetNode1(), GetNode2(), true);
                    }
                
                    added = true;
                } catch (IOException e) {
                    Logger.getLogger(MainGUI.class.getName()).log(Level.SEVERE, null, e);
                }
            }
        }
        if (added) {
            SaveRdfModel(_rdfModelFile);
        }
        return added;
    }

    protected boolean VerifyNodes() {

        Boolean repair = false;
        Vector files = GetAllManagedFiles();
        Iterator fileiter = files.iterator();

        while (fileiter.hasNext()) {
            ManagedFile mf = (ManagedFile) fileiter.next();

            repair = mf.VerifyNodes();

        }
        if (repair) {
            SaveRdfModel(_rdfModelFile);
        }
        return repair;

    }

    public File GetPhysicalFile(String fileName) {

        ManagedFile managedFile = GetManagedFile(fileName);
        File nodeLocation = new File(managedFile.FirstNode.GetLocation());
        if (!nodeLocation.exists()) {
            nodeLocation = new File(managedFile.SecondNode.GetLocation());
        }

        return new File(nodeLocation.getPath() + File.separatorChar + fileName);
    }
}
